#include <memory>
#include <iostream>

#include"scheduler.h"
#include"log.h"
#include"macro.h"
#include"hook.h"


namespace qtch{

static qtch::Logger::ptr logger = QTCH_LOG_NAME("system");
static thread_local Fiber::ptr t_scheduler_fiber = nullptr;
static thread_local Scheduler* t_scheduler = nullptr;


Fiber::ptr Scheduler::GetMainFiber(){
    return t_scheduler_fiber;
}

Scheduler::Scheduler(size_t threads,bool use_caller ,const std::string& name){
    QTCH_ASSERT(threads>0);
    m_name = name;
    m_userCaller = use_caller;
    if(use_caller){
        threads--;
        QTCH_ASSERT(getThis() == nullptr);
        qtch::Thread::SetName(name);
    }
    else{
        m_rootThread = -1;
    }
    m_threadCount = threads;

}

Scheduler::~Scheduler(){
    QTCH_LOG_DEBUG(logger) << "~Scheduler name=" << getName();
    QTCH_ASSERT(m_stopping);
    if(getThis() == this){
        t_scheduler = nullptr;
    }
    QTCH_LOG_DEBUG(logger) << "Scheduler end "
                    << ",name="<<m_name;
}

void Scheduler::start(){
    {
        Scheduler::MutexType::Lock lock(m_mutex);
        if(!m_stopping){
            return;
        }
        m_stopping = false;
        QTCH_ASSERT(m_threads.empty());
        m_threads.resize(m_threadCount);
        for(size_t i=0; i < m_threadCount; ++i){
            m_threads[i].reset(new qtch::Thread(std::bind(&Scheduler::run,this),m_name + "_" + std::to_string(i)));
            m_threadIds.push_back(m_threads[i]->getId());
        }
    }
    if(m_userCaller){
        m_rootThread = qtch::GetThreadId();
        m_threadIds.push_back(m_rootThread);
        run();
    }
    
}

void Scheduler::run(){
    if(m_autoStop){
        return;
    }
    set_hook_enable(true);
    setThis();
    QTCH_LOG_DEBUG(logger) << "begin scheduler run, "
                        << "Scheduler name:"<<m_name;
    t_scheduler_fiber = Fiber::GetThis();
    Fiber::ptr cb_fiber = nullptr;
    Fiber::ptr idle_fiber(new Fiber(std::bind(&Scheduler::idle,this)));

    FiberAndThread ft;
    while(true){
        ft.reset();
        {
            MutexType::Lock lock(m_mutex);
            auto it = m_fibers.begin();
            while(it != m_fibers.end()){
                if(it->threadId != -1 && it -> threadId != qtch::GetThreadId()){
                    ++it;
                    continue;
                }
                QTCH_ASSERT(it->fiber || it->cb);
                if(it->fiber&& it->fiber->getState() == Fiber::State::EXEC){
                    ++it;
                    continue;
                }
                ft = *it;
                m_fibers.erase(it);
                break;
            }
        }
        if(ft.fiber){
            if(!(ft.fiber->getState()==Fiber::EXCEPT||ft.fiber->getState()==Fiber::TERM)){
                ++m_activeThreadCount;
                ft.fiber->swapIn();
                --m_activeThreadCount;
                if(ft.fiber->getState() == Fiber::READY){
                    schedule(ft.fiber,ft.threadId);
                }
            }
        }
        else if(ft.cb){
            if(cb_fiber){
                cb_fiber->reset(ft.cb);
            }
            else{
                cb_fiber.reset(new Fiber(ft.cb));
            }
            ++m_activeThreadCount;
            cb_fiber->swapIn();
            --m_activeThreadCount;
            if(cb_fiber->getState() == Fiber::READY){
                schedule(cb_fiber,ft.threadId);
            }
            cb_fiber.reset();
        }
        else{
            

            if(idle_fiber->getState() == Fiber::TERM){
                QTCH_LOG_DEBUG(logger) << "idle fiber term";
                break;
            }
            else if(idle_fiber->getState() == Fiber::EXCEPT){
                QTCH_LOG_ERROR(logger) << "idle fiber except";
                break;
            }

            ++m_idleThreadCount;
            idle_fiber->swapIn();
            --m_idleThreadCount;

            if(idle_fiber->getState() == Fiber::EXCEPT){
                QTCH_LOG_ERROR(logger) << "idle fiber except";
                break;
            }

        }
        ft.reset();
    }
    if(idle_fiber->getState() != Fiber::TERM 
            &&idle_fiber->getState() != Fiber::EXCEPT
            &&idle_fiber->getState() != Fiber::TERM){
        ++m_idleThreadCount;
        idle_fiber->swapIn();
        --m_idleThreadCount;
    }
    Fiber::SetThis(nullptr);
    t_scheduler = nullptr;
    idle_fiber.reset();
    t_scheduler_fiber.reset();
}

void Scheduler::stop(){
    QTCH_LOG_DEBUG(logger) <<  "Schedule stop "
                    << ",stop threadId=" <<qtch::GetThreadId();
    if(stopping()){
        return;
    }
    m_autoStop = true;

    m_stopping = true;

    for(size_t i = 0; i< m_threadCount; ++i){
        tickle();
    }
    if(m_userCaller){
        tickle();
    }
    std::vector<Thread::ptr> thrs;
    {
        MutexType::Lock lock(m_mutex);
        thrs.swap(m_threads);
    }
    for(auto & i:thrs){
        if(i->getId()==qtch::GetThreadId()){
            continue;
        }
        i->join();
    }
}

bool Scheduler::stopping(){
    MutexType::Lock lock(m_mutex);
    return m_autoStop
        &&m_stopping
        &&m_fibers.empty()
        &&m_activeThreadCount==0;
}

void Scheduler::tickle(){
    QTCH_LOG_INFO(logger) << "tickle";
}

void Scheduler::idle(){
    QTCH_LOG_INFO(logger) << "idle start";
    while(!stopping()){
        Fiber::yieldToHold();
    }
    QTCH_LOG_INFO(logger) << "idle end";
}

Scheduler* Scheduler::getThis(){
    return t_scheduler;
}

void Scheduler::setThis(){
    t_scheduler = this;
}

std::ostream& Scheduler::dump(std::ostream& os){
    os << "[Scheduler name=" << m_name
       << " size=" << m_threadCount
       << " active_count=" << m_activeThreadCount
       << " idle_count=" << m_idleThreadCount
       << " stopping=" << m_stopping
       << " ]";
    os << "\n   ";
    for(size_t i = 0; i << m_threadIds.size(); ++i){
        if(i){
            os << ", ";
        }
        os << m_threadIds[i];
    }
    return os;
}





}

